/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package im.resources.model;

import im.general.Config;
import im.numgenerator.PseudoRandomGenerator;

/**
 *
 * @author Gnaskky
 */
public class ResourcesModel {

    PseudoRandomGenerator prg;

    double node_capabilities;

    int attachedNodes;

    boolean isFixPlugged;

    int physicalRamMB;
    int physicalCPUMhz;
    int physicalDiskMB;

    double physicalBWKbps;
    double physicalBatteryMah;
    double physicalBatteryThicks;

    double busyRamRatio;
    double busyCPURatio;
    double busyDiskRatio;
    double busyBWRatio;
    double usedBatteryRatio;

    double wrapBusyRamRatio;
    double wrapBusyCPURatio;
    double wrapBusyDiskRatio;
    double wrapBusyBWRatio;
    double wrapUsedBatteryRatio;

    double mAhThicksRatio;
    double deltaCPUDischargeRatio;
    double deltaBWDischargeRatio;

    int freeRamMB;
    int freeCPUMhz;
    int freeDiskMB;
    double freeBWKbps;
    double chargeReminderMah;

    public ResourcesModel(PseudoRandomGenerator prg) {
        this.prg = prg;
        this.attachedNodes = 0;
        allocatePhysicalResources();
    }


    protected void allocatePhysicalResources() {
        node_capabilities = prg.getGaussian( Config.NODE_CAPABILITY_MEAN, Config.NODE_CAPABILITY_VAR);

        if( node_capabilities < Config.MIN_CAPABILITIES )
            node_capabilities = Config.MIN_CAPABILITIES;

        if( node_capabilities > Config.MAX_CAPABILITIES )
            node_capabilities = Config.MAX_CAPABILITIES;

        setPhysicalRamMB(node_capabilities*prg.getGaussian(Config.PARAMETER_MEAN_COEFF, Config.PARAMETER_VAR_COEFF));
        setPhysicalCPUMhz(node_capabilities*prg.getGaussian(Config.PARAMETER_MEAN_COEFF, Config.PARAMETER_VAR_COEFF));
        setPhysicalDiskMB(node_capabilities*prg.getGaussian(Config.PARAMETER_MEAN_COEFF, Config.PARAMETER_VAR_COEFF));
        setPhysicalBWKbps(node_capabilities*prg.getGaussian(Config.PARAMETER_MEAN_COEFF, Config.BW_VAR_COEFF));
        setPhysicalBatteryMah(node_capabilities*prg.getGaussian(Config.PARAMETER_MEAN_COEFF, Config.PARAMETER_VAR_COEFF));

        /*if(Config.TRACE_LVL > Config.NORMAL_TR_LVL )
        System.out.println( "node_cap: " + node_capabilities
        + " ph_RamMB: " + physicalRamMB
        + " ph_CPUMhz: " + physicalCPUMhz
        + " ph_DiskMB: " + physicalDiskMB
        + " ph_BWKbps: " + physicalBWKbps
        + " plugged: " + isFixPlugged
        + " ph_BatteryMah: " + physicalBatteryMah );*/

    }


    public boolean updateResources() {
        boolean relevant_change = false;
        relevant_change |= updFreeRamMB();
        relevant_change |= updFreeCPUMHz();
        relevant_change |= updFreeDiskMB();
        relevant_change |= updFreeBWKbps();
        relevant_change |= updChargeReminderMah();

        if(relevant_change) {
            wrapBusyRamRatio = busyRamRatio;
            wrapBusyCPURatio = busyCPURatio;
            wrapBusyDiskRatio = busyDiskRatio;
            wrapBusyBWRatio = busyBWRatio;
            wrapUsedBatteryRatio = usedBatteryRatio;
        }
        return relevant_change;
    }

    // Mbytes
    protected boolean updFreeRamMB(){

        //double old_busy_ratio = busyRamRatio;

        busyRamRatio = updFreeResourcePerc( busyRamRatio,
                            Config.ATT_NODE_RAM_MEAN/(double)physicalRamMB,
                            Config.ATT_NODE_RAM_VAR / (double)physicalRamMB,
                            false );

        freeRamMB = (int) ((double) physicalRamMB * ( 1.0 - busyRamRatio ) );

      /* if(Config.TRACE_LVL > Config.EXTREME_TR_LVL )
            System.out.println( "freeRamMB: " + freeRamMB );*/

       return hadRelevantChange(wrapBusyRamRatio,busyRamRatio );
        
    }

    // MHz
    protected boolean updFreeCPUMHz(){

        //double old_busy_ratio = busyCPURatio;

        busyCPURatio = updFreeResourcePerc( busyCPURatio,
                            Config.ATT_NODE_CPU_MEAN/(double)physicalCPUMhz,
                            Config.ATT_NODE_CPU_VAR / (double)physicalCPUMhz,
                            false );

        freeCPUMhz = (int) ((double) physicalCPUMhz * ( 1.0 - busyCPURatio ) );

      /* if(Config.TRACE_LVL > Config.EXTREME_TR_LVL )
            System.out.println( "freeCPUMhz: " + freeCPUMhz );*/

       return hadRelevantChange(wrapBusyCPURatio,busyCPURatio );
    }

    // MB
    protected boolean updFreeDiskMB(){

       // double old_busy_ratio = busyDiskRatio;

        busyDiskRatio = updFreeResourcePerc( busyDiskRatio,
                            Config.ATT_NODE_DISK_MEAN/(double)physicalDiskMB,
                            Config.ATT_NODE_DISK_VAR / (double)physicalDiskMB,
                            false );

        freeDiskMB = (int) ((double) physicalDiskMB * ( 1.0 - busyDiskRatio ) );

       /*if(Config.TRACE_LVL > Config.EXTREME_TR_LVL )
            System.out.println( "freeDiskMB: " + freeDiskMB );*/

        return hadRelevantChange(wrapBusyDiskRatio,busyDiskRatio );
    }

    // Kbps
    protected boolean updFreeBWKbps(){

        //double old_busy_ratio = busyBWRatio;

        busyBWRatio = updFreeResourcePerc( busyBWRatio,
                            Config.ATT_NODE_BW_MEAN/(double)physicalBWKbps,
                            Config.ATT_NODE_BW_VAR / (double)physicalBWKbps,
                            true );

        freeBWKbps = (int) ((double) physicalBWKbps * ( 1.0 - busyBWRatio ) );

        /*if(Config.TRACE_LVL > Config.EXTREME_TR_LVL )
        System.out.println( "freeBWKbps: " + freeBWKbps );*/

        return hadRelevantChange(wrapBusyBWRatio,busyBWRatio );
    }

    // mAmpere/hour
    protected boolean updChargeReminderMah(){

        // :)
        if( this.isFixPlugged )
            return false;

        //double old_busy_ratio = usedBatteryRatio;

        //discharge by CPU
        chargeReminderMah -= mAhThicksRatio* busyCPURatio*deltaCPUDischargeRatio;
        //discharge by BW usage
        chargeReminderMah -= mAhThicksRatio* busyBWRatio*deltaBWDischargeRatio;

        if ( chargeReminderMah < 0.0 )
            chargeReminderMah = 0.0;

        /*if( Config.TRACE_LVL > Config.EXTREME_TR_LVL )
        System.out.println( "chargeReminderMah: " + chargeReminderMah );*/

        usedBatteryRatio = 1.0 - (chargeReminderMah / physicalBatteryMah);

        if( Config.TRACE_LVL > Config.EXTREME_TR_LVL )
            System.out.println( "chargeReminderMah: " + chargeReminderMah );


        return hadRelevantChange(wrapUsedBatteryRatio,usedBatteryRatio );
    }

    protected double updFreeResourcePerc( double busyPerc, double ch_es_mean, double ch_es_var, boolean add_net_load ){

        double newBusyPerc = 0.0;

        //os resources
        newBusyPerc += prg.getGaussian(Config.OS_RES_FRACTION_MEAN, Config.OS_RES_FRACTION_VAR );
        //user resources
        newBusyPerc += prg.getGaussian(Config.USR_RES_FRACTION_MEAN, Config.USR_RES_FRACTION_VAR );
        //CH & ES resources
        double per_event_load = prg.getGaussian(ch_es_mean, ch_es_var );
        newBusyPerc += ((double) attachedNodes) * per_event_load;

        if( add_net_load )
            newBusyPerc += prg.getGaussian(Config.NET_LOAD_FRACTION_MEAN, Config.NET_LOAD_FRACTION_VAR );
        
        if( busyPerc > 0.0 ) {
            busyPerc *= 1.0 - Config.VARIABILITY;
            busyPerc = Config.VARIABILITY * newBusyPerc;
        } else
            busyPerc = newBusyPerc;

        if( busyPerc > 1.0 )
            busyPerc = 1.0;

        return busyPerc;
    }


    // Mbytes
    protected void setPhysicalRamMB(double num){
        int num1 = (int) (num * 10);
        physicalRamMB = (int)2* ((int) (Math.pow(2.0, num1)));
    }

    // MHz
    protected void setPhysicalCPUMhz (double num){
        physicalCPUMhz =  (int) (1.5 * (num * 1000));
        deltaCPUDischargeRatio = Config.NODE_CPU_BW_DISCHARGE_RATIO * num/this.node_capabilities;
    }

    // MBytes
    protected void setPhysicalDiskMB (double num){

        int ret = 0;

        if(num<0.33) ret= (int) (2500 * num);
        if(num>0.33&&num<0.66) ret= (int) (5000 *num);
        if(num>0.66) ret = (int) (250000*num);

        physicalDiskMB = ret;

    }

    // Kbps
    protected void setPhysicalBWKbps (double x_in){

        deltaBWDischargeRatio = ( 1.0 - Config.NODE_CPU_BW_DISCHARGE_RATIO ) * x_in/this.node_capabilities;

        /* by regression :)
         * y = a * e^bx + c
        0.05 6
        0.1  12
        0.2  56
        0.3  256
        0.4  1000
        0.5  4000
        0.6  10000
        0.85 100000
        1.1  1000000
         * */

		physicalBWKbps = 0.0;

        /*
		// coefficients
		double a = 1.9818780451532403E-01;
		double b = 1.5432263810378300E+01;
		double c = 1.7624874559558882E+03;

		physicalBWKbps = a * Math.exp(b*x_in) + c;
        physicalBWKbps *= .1;
         */

         if( x_in <= 0.4 ) {
            // coefficients
            double a = -4.4206214769242673E+04;
            double b = -2.4869294076480636E-01;
            double c = 1.2005325526363586E+00;
            double d = 2.0077036670832369E+00;

            physicalBWKbps += (a / b) * Math.exp(-0.5 * ((x_in - c) / b) * ((x_in - c) / b)) + d;
         } else {

            // coefficients
            double a = -1.6092545393849955E+03;
            double b = -2.8315735653925918E+02;
            double c = -7.2312382639172679E+00;
            double d = 1.2875207378216473E+00;

            physicalBWKbps = a - b*Math.exp(-1.0 * c * Math.pow(x_in, d));
         }
    }

       // Kbps
    protected void setPhysicalBatteryMah (double x_in){

        /* by regression :)
         * y = a * e^bx + c
0.05 500
0.2 1500
0.4 3000
0.5 5000
0.7 8000
1.1 20000

         * */

		physicalBatteryMah = 0.0;

        // coefficients
		double a = -1.2114491214987308E+02;
		double b = 5.1602440246799999E+00;
		double c = -1.2085146722376461E+01;
		double d = -5.2383244317821877E+01;
		double e = -7.7381047937197536E+02;

		physicalBatteryMah = (a*x_in * (1.0 + b*Math.exp(c*x_in))) * (d * Math.exp(x_in)) + e;

        physicalBatteryThicks = Config.NODE_CHARGE_LIFE_MEAN_THICKS * x_in/this.node_capabilities ;
        mAhThicksRatio = physicalBatteryMah/physicalBatteryThicks;
        
        
        this.isFixPlugged = prg.getUniform() < node_capabilities;
        //.usedChargeRatio = 0.0;
        this.chargeReminderMah = physicalBatteryMah;
        // this.chargeReminderThicks = physicalBatteryThicks;

    }

    public int getAttachedNodes() {
        return attachedNodes;
    }

    public void setAttachedNodes(int attachedNodes) {
        this.attachedNodes = attachedNodes;
    }

    public double getChargeReminderMah() {
        return chargeReminderMah;
    }

    public double getFreeBWKbps() {
        return freeBWKbps;
    }

    public int getFreeCPUMhz() {
        return freeCPUMhz;
    }

    public int getFreeDiskMB() {
        return freeDiskMB;
    }

    public int getFreeRamMB() {
        return freeRamMB;
    }

    public boolean isIsFixPlugged() {
        return isFixPlugged;
    }

    protected boolean hadRelevantChange(double oldval, double newval ) {
        double var = oldval - newval;
        if ( var < 0.0 ) var *= -1.0;

        return ( var > Config.STAT_DELTA_RATIO_MIN_WRAP );
    }

}
